package com.fs.common.utils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 日期操作辅助类
 *
 * @author LiuQi 2019/8/21-10:43
 * @version V1.0
 **/
public class DateUtils {
    /**
     * 从开始日期到结束日期顺序执行指定的函数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @param consumer  待执行函数
     */
    public static void callBetween(
            LocalDate startDate,
            LocalDate endDate,
            Consumer<LocalDate> consumer
    ) {
        if (null == startDate || null == endDate || startDate.isAfter(endDate)) {
            return;
        }

        while (!endDate.isBefore(startDate)) {
            consumer.accept(startDate);

            startDate = startDate.plusDays(1);
        }
    }

    /**
     * 从开始日期到结束日期顺序执行转换函数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @param converter 转换函数
     * @param <T>       转换结果对象类型
     * @return 转换结果集
     */
    public static <T> List<T> callBetween(
            LocalDate startDate,
            LocalDate endDate,
            Function<LocalDate, T> converter
    ) {
        List<T> dataList = new ArrayList<>(16);
        callBetween(startDate, endDate, pDate -> {
            dataList.add(converter.apply(pDate));
        });

        return dataList;
    }

    /**
     * 在某个日期区间内按月执行指定函数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @param converter 需要执行的函数
     * @param <T>       返回的对象类型
     * @return 处理结果
     */
    public static <T> List<T> monthCallBetween(
            LocalDate startDate,
            LocalDate endDate,
            Function<LocalDate, T> converter
    ) {
        Function<LocalDate, LocalDate> nextDateFunction = pDate -> pDate.plusMonths(1);

        return callBetween(startDate, endDate, converter, nextDateFunction);
    }

    /**
     * 在某个日期区间内按月执行指定函数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @param consumer  需要执行的函数
     */
    public static void monthCallBetween(
            LocalDate startDate,
            LocalDate endDate,
            Consumer<LocalDate> consumer
    ) {
        monthCallBetween(startDate, endDate, date -> {
            consumer.accept(date);
            return null;
        });
    }

    /**
     * 在某个日期区间内按年执行指定函数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @param converter 需要执行的函数
     * @param <T>       返回的对象类型
     * @return 处理结果
     */
    public static <T> List<T> yearCallBetween(
            LocalDate startDate,
            LocalDate endDate,
            Function<LocalDate, T> converter
    ) {
        return callBetween(startDate, endDate, converter, pDate -> pDate.plusYears(1));
    }

    /**
     * 在指定日期区间内执行指定函数，其中日期跳转按nextDateFunction的方式来进行
     * 如间隔一天调用一次处理函数；或者间隔两天；或者间隔一个月；或者间隔一年等；
     *
     * @param startDate        开始日期
     * @param endDate          结束日期
     * @param converter        需要执行的函数
     * @param nextDateFunction 由当前处理日期获取下一处理日期的转换函数
     * @param <T>              返回数据类型
     * @return 处理结果
     */
    private static <T> List<T> callBetween(
            LocalDate startDate,
            LocalDate endDate,
            Function<LocalDate, T> converter,
            Function<LocalDate, LocalDate> nextDateFunction
    ) {
        if (null == startDate || null == endDate || null == converter) {
            return new ArrayList<>(0);
        }

        LocalDate pDate = startDate;
        List<T> list = new ArrayList<>(16);
        while (!pDate.isAfter(endDate)) {
            list.add(converter.apply(pDate));

            pDate = nextDateFunction.apply(pDate);
        }

        return list;
    }

    public static LocalDate toLocalDate(Date date) {
        if (null == date) {
            return null;
        }

        return LocalDate.from(date.toInstant());
    }

    public static LocalDateTime toLocalDateTime(Date date) {
        if (null == date) {
            return null;
        }

        return LocalDateTime.from(date.toInstant());
    }
}
